#ifndef FHE_FUNC_H
#define FHE_FUNC_H

#include <fhe/core/IFunc.h>
#include <boost/preprocessor/repetition.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/arithmetic/sub.hpp>

namespace fhe
{
  namespace core
  {
    
    template < typename T, typename R, BOOST_PP_ENUM_PARAMS( FHE_ARGS, typename A ) >
    class Func;
    
    #define FUNC_id( z, n, data ) data
    
    #define FUNC_typeif( z, n, unused ) if ( args[n].is< BOOST_PP_CAT( A, n ) >() )
    
    #define FUNC_typecheck( z, n, unused ) FHE_ASSERT_MSG( args[n].is< BOOST_PP_CAT( A, n ) >(), "arg %d type mismatch: got %s expected %s", n, args[n].type().c_str(), typeid( BOOST_PP_CAT( A, n ) ).name() );
    
    #define FUNC_arg( z, n, unused ) args[n].get< BOOST_PP_CAT( A, n ) >()
    
    #define RETFUNC_iter( z, n, unused )  \
      template <typename T, typename R BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A )> \
      class Func<T,R, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNC_id, void )> : public IFunc \
      { \
      public: \
        typedef R (T::*F)( BOOST_PP_ENUM_PARAMS( n, A ) ); \
        Func( T* t, F f, const std::string& name ) \
          : t_( t ) \
          , f_( f ) \
          , name_( name ) \
        { \
        } \
        Val call( const std::vector< Val >& args ) const \
        { \
          FHE_ASSERT_MSG( args.size() == n, "func arg count mismatch: got %d expected %d", args.size(), n ); \
          BOOST_PP_REPEAT( n, FUNC_typecheck, ~ ) \
          return (t_->*f_)( BOOST_PP_ENUM( n, FUNC_arg, ~ ) ); \
        } \
        std::string name() const \
        { \
          return name_; \
        } \
      private: \
        T* t_; \
        F f_; \
        std::string name_; \
      };
      
    BOOST_PP_REPEAT( FHE_ARGS, RETFUNC_iter, ~ )
    
    #undef RETFUNC_iter
    
    #define FUNC_iter( z, n, unused ) \
      template <typename T BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A)> \
      class Func<T,void, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNC_id, void )> : public IFunc \
      { \
      public: \
        typedef void (T::*F)( BOOST_PP_ENUM_PARAMS( n, A ) ); \
        Func( T* t, F f, const std::string& name ) \
          : t_( t ) \
          , f_( f ) \
          , name_( name ) \
        { \
        } \
        Val call( const std::vector< Val >& args ) const \
        { \
          FHE_ASSERT_MSG( args.size() == n, "func arg count mismatch: got %d expected %d", args.size(), n ); \
          BOOST_PP_REPEAT( n, FUNC_typecheck, ~ ) \
          (t_->*f_)( BOOST_PP_ENUM( n, FUNC_arg, ~ ) ); \
          return Val(); \
        } \
        std::string name() const \
        { \
          return name_; \
        } \
      private: \
        T* t_; \
        F f_; \
        std::string name_; \
      };
      
    BOOST_PP_REPEAT( FHE_ARGS, FUNC_iter, ~ )
    
    #undef FUNC_iter

    template < typename T, typename R, BOOST_PP_ENUM_PARAMS( FHE_ARGS, typename A ) >
    class ConstFunc;
    
    #define CONSTFUNC_iter( z, n, unused ) \
      template <typename T BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A)> \
      class ConstFunc<T,void, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNC_id, void )> : public IFunc \
      { \
      public: \
        typedef void (T::*F)( BOOST_PP_ENUM_PARAMS( n, A ) ) const; \
        ConstFunc( T* t, F f, const std::string& name ) \
          : t_( t ) \
          , f_( f ) \
          , name_( name ) \
        { \
        } \
        Val call( const std::vector< Val >& args ) const \
        { \
          FHE_ASSERT_MSG( args.size() == n, "func arg count mismatch: got %d expected %d", args.size(), n ); \
          BOOST_PP_REPEAT( n, FUNC_typecheck, ~ ) \
          (t_->*f_)( BOOST_PP_ENUM( n, FUNC_arg, ~ ) ); \
          return Val(); \
        } \
        std::string name() const \
        { \
          return name_; \
        } \
      private: \
        T* t_; \
        F f_; \
        std::string name_; \
      };
      
    BOOST_PP_REPEAT( FHE_ARGS, CONSTFUNC_iter, ~ )
    
    #undef CONSTFUNC_iter

    #define CONSTRETFUNC_iter( z, n, unused )  \
      template <typename T, typename R BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A )> \
      class ConstFunc<T,R, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNC_id, void )> : public IFunc \
      { \
      public: \
        typedef R (T::*F)( BOOST_PP_ENUM_PARAMS( n, A ) ) const; \
        ConstFunc( T* t, F f, const std::string& name ) \
          : t_( t ) \
          , f_( f ) \
          , name_( name ) \
        { \
        } \
        Val call( const std::vector< Val >& args ) const \
        { \
          FHE_ASSERT_MSG( args.size() == n, "func arg count mismatch: got %d expected %d", args.size(), n ); \
          BOOST_PP_REPEAT( n, FUNC_typecheck, ~ ) \
          return (t_->*f_)( BOOST_PP_ENUM( n, FUNC_arg, ~ ) ); \
        } \
        std::string name() const \
        { \
          return name_; \
        } \
      private: \
        T* t_; \
        F f_; \
        std::string name_; \
      };
      
    BOOST_PP_REPEAT( FHE_ARGS, CONSTRETFUNC_iter, ~ )
    
    #undef CONSTRETFUNC_iter
    
    #undef FUNC_arg
    #undef FUNC_typecheck
    #undef FUNC_typeif
    #undef FUNC_id
  }
}

#endif
